/*
 * @(#)MarshallUtils.java
 * Date 2013-08-08
 * Version 1.0
 * Author Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.utils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;

import java.util.Properties;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;

import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import org.xml.sax.SAXException;


/**
 *
 * @author jim
 */
public class MarshallUtils {

    /** Field description */
    private static final Logger logger = LoggerFactory.getLogger(MarshallUtils.class);

    /** Field description */
    private static final DocumentBuilderFactory documentBuilderFactory =
        DocumentBuilderFactory.newInstance();

    /**
     * Method description
     *
     *
     * @param obj
     * @param clazz
     * @param <T>
     *
     * @return
     *
     * @throws Exception
     */
    public static <T> String marshall(T obj, Class<T> clazz) throws Exception {

        return marshall(obj, clazz, true);
    }

    /**
     * Method description
     *
     *
     * @param obj
     * @param clazz
     * @param pretty
     * @param <T>
     *
     * @return
     *
     * @throws Exception
     */
    public static <T> String marshall(T obj, Class<T> clazz, boolean pretty)
            throws Exception {

        JAXBContext jaxb = JAXBContext.newInstance(clazz);

        Marshaller m = jaxb.createMarshaller();

        m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.valueOf(pretty));
        
        StringWriter sw = new StringWriter();

        m.marshal(obj, sw);

        return sw.toString();
    }

    /**
     * Method description
     *
     *
     * @param source
     *
     * @return
     *
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws SAXException
     */
    public static Node toXMLNode(InputStream source)
            throws SAXException, IOException, ParserConfigurationException {

        DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();

        Document result = dBuilder.parse(source);

        result.getDocumentElement().normalize();

        return result.getDocumentElement();
    }

    /**
     * Method description
     *
     *
     * @param jaxbobj
     * @param clazz
     * @param <T>
     *
     * @return
     *
     * @throws Exception
     */
    public static <T> Node toXMLNode(T jaxbobj, Class<T> clazz) throws Exception {

        DocumentBuilder dBuilder = documentBuilderFactory.newDocumentBuilder();
        Document result = dBuilder.newDocument();

        JAXBContext jaxb = JAXBContext.newInstance(clazz);

        Marshaller m = jaxb.createMarshaller();

        m.marshal(jaxbobj, result);

        result.getDocumentElement().normalize();

        return result.getDocumentElement();
    }

    /**
     * Method description
     *
     *
     * @param source
     *
     * @return
     */
    public static String toXMLString(Source source) {

        String result = "Unable to pretty print";

        Properties props = new Properties();

        props.setProperty(OutputKeys.INDENT, "yes");
        props.setProperty(OutputKeys.STANDALONE, "yes");
        props.setProperty(OutputKeys.METHOD, "xml");
        props.setProperty("{http://xml.apache.org/xslt}indent-amount", "3");

        try {

            Transformer transformer = TransformerFactory.newInstance().newTransformer();

            transformer.setOutputProperties(props);

            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            StreamResult stream = new StreamResult(bos);

            transformer.transform(source, stream);
            result = bos.toString();

        } catch (TransformerFactoryConfigurationError | TransformerException e) {

            logger.debug("Exception", e);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param xml
     * @param clz
     * @param <T>
     *
     * @return
     *
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T unmarshall(InputStream xml, Class<T> clz) throws Exception {

        T result = null;

        if (xml != null) {

            JAXBContext jaxb = JAXBContext.newInstance(clz);

            Unmarshaller um = jaxb.createUnmarshaller();

            result = (T) um.unmarshal(xml);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param node
     * @param clz
     * @param <T>
     *
     * @return
     *
     * @throws Exception
     */
    public static <T> T unmarshall(Node node, Class<T> clz) throws Exception {

        T result = null;

        if (node != null) {

            JAXBContext jaxb = JAXBContext.newInstance(clz);

            Unmarshaller um = jaxb.createUnmarshaller();

            JAXBElement<T> jaxelem = um.unmarshal(node, clz);

            if (jaxelem != null) {

                result = jaxelem.getValue();
            }
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param xml
     * @param clz
     * @param <T>
     *
     * @return
     *
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T unmarshall(String xml, Class<T> clz) throws Exception {

        T result = null;

        if (StringUtils.isNotBlank(xml)) {

            JAXBContext jaxb = JAXBContext.newInstance(clz);

            Unmarshaller um = jaxb.createUnmarshaller();

            result = (T) um.unmarshal(new StringReader(xml));
        }

        return result;
    }
}
